package com.xiguanlezz.cn.client.naming.core;

import com.xiguanlezz.cn.api.PropertyKeyConst;
import com.xiguanlezz.cn.api.naming.pojo.ServiceInfo;
import com.xiguanlezz.cn.api.naming.utils.NamingUtils;
import com.xiguanlezz.cn.client.env.NacosClientProperties;
import com.xiguanlezz.cn.client.naming.cache.ServiceInfoHolder;
import com.xiguanlezz.cn.client.naming.remote.NamingClientProxy;
import com.xiguanlezz.cn.common.executor.NameThreadFactory;
import com.xiguanlezz.cn.common.utils.ConvertUtils;
import com.xiguanlezz.cn.common.utils.ThreadUtils;
import org.apache.commons.collections.CollectionUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author：习惯了种种
 * @CreateDate：2024/11/28 13:12
 * @Description：定时拉取服务端服务实例信息的实体类
 */
public class ServiceInfoUpdateService {
    // 客户端关于服务实例缓存的实体类
    private ServiceInfoHolder serviceInfoHolder;
    // 客户端代理对象
    private final NamingClientProxy namingClientProxy;
    // 是否开启异步查询服务端服务实例信息的标志
    private final boolean asyncQuerySubscribeService;

    private ScheduledExecutorService executor;

    // 存储定时任务的map，key就是订阅的服务名称，value就是对应的定时任务
    private final Map<String, ScheduledFuture<?>> futureMap = new HashMap<>();

    private static final long DEFAULT_DELAY = 1000L;
    private static final int MIN_THREAD_NUM = 1;


    public ServiceInfoUpdateService(NacosClientProperties properties,
                                    ServiceInfoHolder serviceInfoHolder,
                                    NamingClientProxy namingClientProxy) {
        // 在这里给asyncQuerySubscribeService赋值，如果为true，那么定时任务执行器就会开始工作，定期异步访问服务端拉取服务实例信息
        this.asyncQuerySubscribeService = isAsyncQueryForSubscribeService(properties);
        // 创建定时任务执行器
        this.executor = new ScheduledThreadPoolExecutor(
                initPollingThreadCount(properties),
                new NameThreadFactory("com.alibaba.nacos.client.naming.updater")
        );
        // 客户端关于服务实例缓存的实体类
        this.serviceInfoHolder = serviceInfoHolder;
        // 客户端代理对象
        this.namingClientProxy = namingClientProxy;
    }


    private boolean isAsyncQueryForSubscribeService(NacosClientProperties properties) {
        // 根据配置决定是否需要客户端主动拉取并更新实例信息
        if (Objects.isNull(properties) || !properties.containsKey(PropertyKeyConst.NAMING_ASYNC_QUERY_SUBSCRIBE_SERVICE)) {
            return false;
        }
        return ConvertUtils.toBoolean(properties.getProperty(PropertyKeyConst.NAMING_ASYNC_QUERY_SUBSCRIBE_SERVICE), false);
    }

    private int initPollingThreadCount(NacosClientProperties properties) {
        int count = ThreadUtils.getSuitableThreadCount(1) > 1 ? ThreadUtils.getSuitableThreadCount(1) / 2 : 1;
        if (properties == null) {
            return count;
        }
        count = Math.min(properties.getInteger(PropertyKeyConst.NAMING_POLLING_MAX_THREAD_COUNT, count), count);
        count = Math.max(count, MIN_THREAD_NUM);
        return properties.getInteger(PropertyKeyConst.NAMING_POLLING_THREAD_COUNT, count);
    }

    /**
     * 启动某个订阅服务的定时任务
     */
    public void scheduleUpdateIfAbsent(String serviceName, String groupName, String clusters) {
        if (!asyncQuerySubscribeService) {
            // 如果没开启异步查询服务，就直接退出
            return;
        }
        // 根据服务信息得到key
        String serviceKey = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);
        // 判断futureMap中是否已经存在对应的定时任务了
        if (futureMap.containsKey(serviceKey)) {
            return;
        }
        synchronized (futureMap) {
            // 二次判断
            if (futureMap.containsKey(serviceKey)) {
                return;
            }
            // 没有则创建对应的定时任务，然后直接把定时任务提交给定时任务执行器
            ScheduledFuture<?> future = addTask(new UpdateTask(serviceName, groupName, clusters));
            futureMap.put(serviceKey, future);
        }
    }

    private synchronized ScheduledFuture<?> addTask(UpdateTask task) {
        return executor.schedule(task, DEFAULT_DELAY, TimeUnit.MILLISECONDS);
    }

    /**
     * 停止某个订阅服务的定时任务
     */
    public void stopUpdateIfContain(String serviceName, String groupName, String clusters) {
        String serviceKey = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);
        if (!futureMap.containsKey(serviceKey)) {
            return;
        }
        synchronized (futureMap) {
            if (!futureMap.containsKey(serviceKey)) {
                return;
            }
            futureMap.remove(serviceKey);
        }
    }

    class UpdateTask implements Runnable {
        int failCount = 0; /* 用于实现退避重试 */
        long lastRefTime = Long.MAX_VALUE;
        String serviceName;
        String groupName;
        String cluster;

        public UpdateTask(String serviceName, String groupName, String clusters) {
            this.serviceName = serviceName;
            this.groupName = groupName;
            this.cluster = clusters;
        }

        @Override
        public void run() {
            long delayTime = DEFAULT_DELAY;
            try {
                // 获得serviceKey
                String serviceKey = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), cluster);

                ServiceInfo serviceInfo = serviceInfoHolder.getServiceInfoMap().get(serviceKey);
                if (Objects.isNull(serviceInfo)) {
                    // 调用客户端代理对象查询服务端最新的实例信息
                    // 更新缓存类中的实例信息
                    // 更新lastRefTime
                    return;
                }

                if (serviceInfo.getLastRefTime() <= lastRefTime) {
                    // 调用客户端代理对象查询服务端最新的实例信息
                    // 更新缓存类中的实例信息
                }
                // 更新lastRefTime
                if (CollectionUtils.isEmpty(serviceInfo.getHosts())) {
                    incFailCount();
                }
            } finally {
                // 下次再次执行，实现了退避逻辑
                executor.schedule(
                        this,
                        Math.min(delayTime << failCount, DEFAULT_DELAY * 60),
                        TimeUnit.MILLISECONDS
                );
                resetFailCount();
            }
        }

        private void incFailCount() {
            int limit = 6;
            if (failCount == limit) {
                return;
            }
            failCount++;
        }

        private void resetFailCount() {
            failCount = 0;
        }
    }

}
